﻿using ICSharpCode.SharpZipLib.Zip;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Net.Mime;

namespace Dotnet.Utils.Utility.Zip
{
    /// <summary>
    /// 解压缩工具
    /// </summary>
    /// <remarks>
    /// 包括压缩方法和解压方法。
    /// </remarks>
    public class ZipUtil
    {
        /// <summary>
        /// 压缩文件到ZIP
        /// </summary>
        /// <param name="sourceFile">要压缩的源文件</param>
        /// <param name="zipFile">压缩后存储的ZIP文件</param>
        /// <param name="ifDeleteExistZipFile">如果目标ZIP文件，是否删除之，默认删除</param>
        /// <returns>压缩成功，返回<c>true</c></returns>
        public static bool ZipFile(string sourceFile, string zipFile, bool ifDeleteExistZipFile = true)
        {
            try
            {
                if (!File.Exists(sourceFile))
                    throw new Exception("要压缩的源文件不存在");

                if (File.Exists(zipFile) && ifDeleteExistZipFile)
                    File.Delete(zipFile);

                using (var package = Package.Open(zipFile, FileMode.Create))
                {
                    string destFilename = ".\\" + Path.GetFileName(sourceFile);
                    Uri uri = PackUriHelper.CreatePartUri(new Uri(destFilename, UriKind.Relative));
                    if (package.PartExists(uri))
                        package.DeletePart(uri);
                    PackagePart part = package.CreatePart(uri, MediaTypeNames.Application.Zip);

                    var fi = new FileInfo(sourceFile);
                    using (var fs = fi.OpenRead())
                    {
                        //复制源文件的内容到Package里面
                        CopyStream(fs, part.GetStream());
                    }
                }

                //检查最后的压缩成果，如果压缩文件正常创建了，返回true
                if (File.Exists(zipFile))
                    return true;
            }
            catch (Exception)
            {
                throw;
            }

            return false;
        }

        /// <summary>
        /// 压缩多个文件到ZIP
        /// </summary>
        /// <param name="sourceFiles">要压缩的源文件列表</param>
        /// <param name="zipFile">压缩后存储的ZIP文件</param>
        /// <param name="zipFiles">压缩包中包含的文件列表</param>
        /// <param name="ifDeleteExistZipFile">如果目标ZIP文件，是否删除之，默认删除</param>
        /// <returns>压缩成功，返回<c>true</c></returns>
        public static bool ZipFiles(IEnumerable<string> sourceFiles, string zipFile, out List<string> zipFiles, bool ifDeleteExistZipFile = true)
        {
            zipFiles = new List<string>();
            try
            {
                if (sourceFiles == null || !sourceFiles.Any())
                    throw new ArgumentNullException("sourceFiles");

                var needZipFiles = sourceFiles.Where(x => File.Exists(x))
                    .GroupBy(x => x)
                    .Select(g => g.Key);
                if (needZipFiles == null || !needZipFiles.Any())
                    throw new Exception("没有需要压缩的文件");

                if (File.Exists(zipFile) && ifDeleteExistZipFile)
                    File.Delete(zipFile);

                needZipFiles.ToList().ForEach(fileToAdd =>
                {
                    using (var package = Package.Open(zipFile, FileMode.OpenOrCreate))
                    {
                        string destFilename = ".\\" + Path.GetFileName(fileToAdd);
                        Uri uri = PackUriHelper.CreatePartUri(new Uri(destFilename, UriKind.Relative));
                        if (package.PartExists(uri))
                            package.DeletePart(uri);

                        PackagePart part = package.CreatePart(uri, "", CompressionOption.Normal);
                        using (var fs = new FileStream(fileToAdd, FileMode.Open, FileAccess.Read))
                        {
                            //复制源文件的内容到Package里面
                            CopyStream(fs, part.GetStream());
                        }
                    }
                });

                zipFiles = needZipFiles.GroupBy(x => Path.GetFileName(x)).Select(g => g.Key).ToList();

                //检查最后的压缩成果，如果压缩文件正常创建了，返回true
                if (File.Exists(zipFile))
                    return true;
            }
            catch (Exception)
            {
                throw;
            }

            return false;
        }

        /// <summary>
        /// 将ZIP解压到指定目录下
        /// </summary>
        /// <param name="zipFile">要解压的ZIP文件</param>
        /// <param name="targetFolder">解压到的目标目录</param>
        /// <param name="outFiles">解压出的文件列表</param>
        /// <param name="ifDeleteExistTargetFile">如果目标目录中存在了压缩包中的文件，是否删除之，默认删除</param>
        /// <returns>压缩成功，返回<c>true</c></returns>
        public static bool UnzipFile(string zipFile, string targetFolder, out List<string> outFiles, bool ifDeleteExistTargetFile = true)
        {
            outFiles = new List<string>();
            try
            {
                if (!File.Exists(zipFile))
                    throw new Exception("要解压的ZIP文件不存在");

                if (!Directory.Exists(targetFolder))
                    Directory.CreateDirectory(targetFolder);

                using (var package = Package.Open(zipFile, FileMode.Open))
                {
                    var parts = package.GetParts();
                    int count = 0;
                    foreach (var part in parts)
                    {
                        //这里有一点需要注意的是需要对URL解码
                        var deURL = System.Web.HttpUtility.UrlDecode(part.Uri.OriginalString);
                        //var deURL = part.Uri.OriginalString;
                        var outFileFullPath = Path.Combine(targetFolder, deURL.Substring(1)).Replace("/", "\\");

                        //确保子集目录存在，不存在创建之，如不创建则后续解压文件时将抛错
                        var outFileName = Path.GetFileName(outFileFullPath);
                        var outFilePath = outFileFullPath.Substring(0, outFileFullPath.Length - outFileName.Length - 1);
                        if (!Directory.Exists(outFilePath))
                            Directory.CreateDirectory(outFilePath);

                        //如果解压后的目标文件存在，根据参数条件确定是否删除
                        if (File.Exists(outFileFullPath) && ifDeleteExistTargetFile)
                            File.Delete(outFileFullPath);

                        var fi = new FileInfo(outFileFullPath);
                        using (var fs = fi.Create())
                        {
                            //复制源文件的内容到Package里面
                            CopyStream(part.GetStream(), fs);
                        }

                        //确定解压该文件成功
                        if (File.Exists(outFileFullPath))
                            outFiles.Add(outFileFullPath);
                        count++;
                    }

                    //如果压缩包中的所有文件都解压缩出来了，成功
                    if (outFiles.Count == count)
                        return true;
                }
            }
            catch (Exception)
            {
                throw;
            }

            return false;
        }

        /// <summary>
        /// 压缩目录到ZIP
        /// </summary>
        /// <param name="sourcePath">要压缩的源目录</param>
        /// <param name="zipFile">压缩后存储的ZIP文件</param>
        /// <param name="zipFiles">压缩包中包含的文件列表</param>
        /// <param name="ifDeleteExistZipFile">如果目标ZIP文件，是否删除之，默认删除</param>
        /// <returns>压缩成功，返回<c>true</c></returns>
        public static bool ZipFolder(string sourcePath, string zipFile, out List<string> zipFiles, bool ifDeleteExistZipFile = true)
        {
            zipFiles = new List<string>();
            try
            {
                if (!Directory.Exists(sourcePath))
                    throw new Exception("要压缩的源文件目录不存在");

                if (File.Exists(zipFile) && ifDeleteExistZipFile)
                    File.Delete(zipFile);

                using (var package = Package.Open(zipFile, FileMode.Create))
                {
                    var di = new DirectoryInfo(sourcePath);
                    //递归遍历所有的子目录和子文件夹
                    ZipDirectory(sourcePath, di, package, zipFiles);
                }

                return true;
            }
            catch (Exception)
            {
                throw;
            }
        }

        ///
        /// </summary>
        /// <param name="di">用于创建、移动和枚举目录和子目录的实例方法</param>
        /// <param name="package">压缩包容器</param>

        /// <summary>
        /// 创建一个函数ZipDirectory，用来递归遍历所有的子目录和子文件夹。
        /// </summary>
        /// <param name="sourcePath">目标文件夹路径</param>
        /// <param name="di">目标文件夹的DirectoryInfo</param>
        /// <param name="package">包</param>
        /// <param name="zipFiles">压缩文件列表</param>
        private static void ZipDirectory(string sourcePath, DirectoryInfo di, Package package, List<string> zipFiles)
        {
            int i = 0;
            foreach (var fi in di.GetFiles())
            {
                i++;
                //对每一个文件，创建一个PackagePart的实例。
                string relativePath = fi.FullName.Replace(sourcePath, "");
                relativePath = relativePath.Replace("\\", "/");

                Uri uri = PackUriHelper.CreatePartUri(new Uri(relativePath, UriKind.Relative));
                if (package.PartExists(uri))
                    package.DeletePart(uri);

                PackagePart part = package.CreatePart(uri, System.Net.Mime.MediaTypeNames.Application.Zip);
                using (var fs = fi.OpenRead())
                {
                    //复制源文件的内容到Package里面
                    CopyStream(fs, part.GetStream());
                }

                //如果文件压缩成功，加入列表
                if (fi.Exists && !zipFiles.Contains(relativePath.Substring(1)))
                    zipFiles.Add(relativePath.Substring(1));
            }

            //递归遍历所有的子目录和子文件夹
            foreach (DirectoryInfo subDi in di.GetDirectories())
            {
                ZipDirectory(sourcePath, subDi, package, zipFiles);
            }
        }

        /// <summary>
        /// 复制流
        /// </summary>
        /// <param name="source">原始流</param>
        /// <param name="target">目标流</param>
        private static void CopyStream(Stream source, Stream target)
        {
            const int bufSize = 0x1000;
            byte[] buf = new byte[bufSize];
            int bytesRead = 0;
            while ((bytesRead = source.Read(buf, 0, bufSize)) > 0)
                target.Write(buf, 0, bytesRead);
        }





        /// <summary>
        /// 压缩数据
        /// </summary>
        /// <param name="sEntryName"><see cref="string"/>压缩名称</param>
        /// <param name="byteData"><see cref="byte"/>[]，待压缩数据</param>
        /// <param name="iOffset"><see cref="string"/>，偏移量</param>
        /// <param name="iLength"><see cref="int"/>，长度</param>
        /// <returns><see cref="byte"/>[]，已压缩数据</returns>
        /// <example>
        /// <code>
        /// byte[] byteData = new byte[100];
        /// byte[] CompressData = ZipUtil.CompressData("rar1",byteData,0,byteData.Length);
        /// </code>
        /// </example>
        public static byte[] CompressData(string sEntryName, byte[] byteData, int iOffset, int iLength)
        {
            MemoryStream zipMemStream = new MemoryStream();
            ZipOutputStream zipStream = new ZipOutputStream(zipMemStream);
            ZipEntry zipEntry = new ZipEntry(sEntryName);
            zipStream.PutNextEntry(zipEntry);
            zipStream.Write(byteData, iOffset, iLength);
            zipStream.Finish();
            byte[] byteRet = zipMemStream.ToArray();
            zipStream.Close();
            zipMemStream.Close();
            return byteRet;
        }

        /// <summary>
        /// 解压数据
        /// </summary>
        /// <param name="byteData"><see cref="byte"/>[]，待解压数据</param>
        /// <param name="iOffset"><see cref="string"/>，偏移量</param>
        /// <param name="iLength"><see cref="int"/>，长度</param>
        /// <returns><see cref="byte"/>[]，解压后数据</returns>
        /// <example>
        /// <code>
        /// byte[] byteData = new byte[100];
        /// byte[] DecompressData = ZipUtil.DecompressData(byteData,0,byteData.Length);
        /// </code>
        /// </example>
        public static byte[] DecompressData(byte[] byteData, int iOffset, int iLength)
        {
            MemoryStream zipMemStream = new MemoryStream(byteData, iOffset, iLength);
            MemoryStream zipTempStream = new MemoryStream();
            ZipInputStream zipStream = new ZipInputStream(zipMemStream);
            ZipEntry zipEntry = zipStream.GetNextEntry();
            if (zipEntry != null)
            {
                int iSize;
                byte[] byteTemp = new byte[8000];
                while (true)
                {
                    iSize = zipStream.Read(byteTemp, 0, byteTemp.Length);
                    if (iSize > 0)
                    {
                        zipTempStream.Write(byteTemp, 0, iSize);
                    }
                    else
                    {
                        break;
                    }
                }
                zipStream.Close();
                zipMemStream.Close();

                byteTemp = zipTempStream.ToArray();
                zipTempStream.Close();
                return byteTemp;
            }
            else
            {
                return null;
            }
        }



    }
}